package solver;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

import model.IncrementalCostCalculator;
import model.IncrementalValidator;
import model.Problem;
import model.Solution;
import model.SolverEnvironment;

import command.IncrementalMoveProcessToMachineCommand;
import command.Invoker;

public class Perturbator {
	SolverEnvironment env;
	long limit = 3000000L;
	long initial_solution_cost;

	Problem problem;
	Solution initial_solution;
	Solution current_solution;
	IncrementalValidator iv;
	IncrementalCostCalculator cc;
	Invoker invoker;
	Random random;

	public Perturbator(SolverEnvironment env) {
		this.env = env;
		this.problem = env.getProblem();
		this.random = env.getRandom();
		this.initial_solution = env.getInitialSolution();
		invoker = env.getInvoker();
		this.current_solution = env.getCurrentSolution();
		iv = env.getValidator();
		cc = env.getCostCalculator();
		env.getFullValidator().computeCost();
		initial_solution_cost = env.getFullValidator().getInitialSolutionCost();
	}

	long local_best_cost_copy = -1;

	public void moveToInferiorSolution(long local_best_cost) {
		if (local_best_cost > local_best_cost_copy)
			limit = (long) (limit * 1.1);
		else
			limit = (long) (limit * 0.9);
		if (local_best_cost + limit > initial_solution_cost) {
			limit = initial_solution_cost - local_best_cost;
		}
		System.out
				.printf("It is allowed to go to a worse solution by %,d from LBC=%,d\n",
						limit, local_best_cost);
		local_best_cost_copy = local_best_cost;

		List<Integer> machinesList = new ArrayList<Integer>();
		for (int m = 0; m < problem.M; m++) {
			machinesList.add(m);
		}

		Problem.VERBOSE = false;

		Collections.shuffle(machinesList, random);
		int moves = 0;
		int i = 0;
		long sum = 0;
		Set<Integer> movedProcesses = new HashSet<Integer>();
		outer: while (true) {
			int m1 = machinesList.get(i % problem.M);
			List<Integer> processes_belonging_to_machine = current_solution
					.getProcessesForMachine(m1);
			if (processes_belonging_to_machine.isEmpty()) {
				i++;
				continue outer;
			}
			int selected_process_id = processes_belonging_to_machine.get(random
					.nextInt(processes_belonging_to_machine.size()));
			if (movedProcesses.contains(new Integer(selected_process_id))) {
				i++;
				continue outer;
			}
			int process_initial_machine = env.getInitialSolution()
					.getMachineIdForProcessId(selected_process_id);
			int pos = -1;
			for (int j = 0; j < problem.M; j++) {
				if (machinesList.get(j) == process_initial_machine)
					pos = j;
			}
			// for (int j = 1; j <= problem.M; j++) {
			// int m2 = machinesList.get((i + j) % problem.M);
			for (int j = 0; j <= problem.M; j++) {
				int m2 = machinesList.get((pos + j) % problem.M);
				if (iv.isProbablyFeasibleNoSchedule(selected_process_id, m1, m2)) {
					long delta = cc.getProcessMoveToMachineCost(
							current_solution, selected_process_id, m1, m2);
					IncrementalMoveProcessToMachineCommand cmd = new IncrementalMoveProcessToMachineCommand(
							iv, cc);
					cmd.setP_id(selected_process_id);
					cmd.setM_id(m2);
					invoker.addCommand(cmd);
					invoker.executeLastCommand();
					if (iv.isFinallyFeasible(selected_process_id, m1, m2)) {
						if (sum + delta <= limit) {
							sum += delta;
							movedProcesses.add(selected_process_id);
							System.out.printf(
									"p%d_im%d_cm%d moved to m%d %,d\n",
									process_initial_machine,
									selected_process_id, m1, m2, delta);
						} else {
							invoker.undoLastCommand();
						}
						moves++;
						if (moves == 30) {
							break outer;
						} else
							continue outer;
					} else {
						invoker.undoLastCommand();
					}
				}
			}
			i++;
		}
	}

	public Solution getPotentialSolution() {
		return env.getPotentialSolution();
	}

}
